package net

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"pan/common/exception"
	"pan/common/util"
	"strconv"
	"strings"
	"time"
)

type HttpMethod int

const (
	HTTP_GET HttpMethod = iota + 1
	HTTP_POST
	HTTP_PUT
)

type HttpResponse struct {
	StatusCode int
	Header     map[string][]string
	Body       []byte
}

func (h *HttpResponse) GetBodyJSON(out interface{}) error {
	if h.Body == nil {
		return exception.NewExpectError("response body is empty")
	}
	if err := json.Unmarshal(h.Body, out); err != nil {
		return err
	}
	return nil
}

func (h *HttpResponse) GetHeader(key string) string {
	if h.Header == nil || len(h.Header) == 0 {
		return ""
	}
	for k, v := range h.Header {
		if strings.EqualFold(k, key) {
			if v == nil {
				return ""
			}
			switch len(v) {
			case 0:
				return ""
			case 1:
				return v[0]
			default:
				strings.Join(v, ",")
			}
		}
	}
	return ""
}

func (h *HttpResponse) ToJSON() string {
	var buff bytes.Buffer
	buff.WriteString("{")
	buff.WriteString("\"StatusCode\": ")
	buff.WriteString(strconv.Itoa(h.StatusCode))
	buff.WriteString(", \"Header\": ")

	header, err := json.Marshal(h.Header)
	if err != nil {
		buff.WriteString("\"parse failed, " + err.Error() + "\"")
	} else {
		buff.Write(header)
	}
	buff.WriteString(", \"Body\": ")
	buff.Write(h.Body)

	buff.WriteString("}")
	return buff.String()
}

func NewHttpResponse() *HttpResponse {
	return &HttpResponse{
		StatusCode: 0,
		Header:     make(map[string][]string),
		Body:       nil,
	}
}

type StatusCodeHandle func(statusCode int) error

type DownloadStart func(dir, filename string, total int64)
type DownloadProgress func(num int64, written int64, total int64)
type DownloadFinish func(written int64, total int64, err error)

type HttpClient struct {
	url    string
	method HttpMethod
	header map[string]string
	form   map[string]string
	body   string

	statusCodeHandle StatusCodeHandle
	downloadStart    DownloadStart
	downloadProgress DownloadProgress
	downloadFinish   DownloadFinish

	err error
}

func NewHttpClient() *HttpClient {
	return &HttpClient{
		method: HTTP_GET,
		header: make(map[string]string),
	}
}

func (h *HttpClient) SetUrl(url string) *HttpClient {
	h.url = url
	return h
}

func (h *HttpClient) SetMethod(method HttpMethod) *HttpClient {
	h.method = method
	return h
}

func (h *HttpClient) SetHeader(header map[string]string) *HttpClient {
	if header != nil {
		h.header = header
	}
	return h
}

func (h *HttpClient) AddHeader(key string, value string) *HttpClient {
	h.header[key] = value
	return h
}

func (h *HttpClient) SetForm(form map[string]string) *HttpClient {
	h.form = form
	return h
}

func (h *HttpClient) AddForm(key string, value string) *HttpClient {
	if h.form == nil {
		h.form = make(map[string]string)
	}
	if key != "" && value != "" {
		h.form[key] = value
	}
	return h
}

func (h *HttpClient) SetBody(body string) *HttpClient {
	h.body = body
	return h
}

func (h *HttpClient) SetBodyJSON(body interface{}) *HttpClient {
	if body == nil {
		return h
	}
	data, err := json.Marshal(body)
	if err != nil {
		h.err = err
		return h
	}
	h.body = string(data)
	return h
}

func (h *HttpClient) SetStatusCodeHandle(handle StatusCodeHandle) *HttpClient {
	if handle != nil {
		h.statusCodeHandle = handle
	}
	return h
}

func (h *HttpClient) SetDownloadStart(start DownloadStart) *HttpClient {
	if start != nil {
		h.downloadStart = start
	}
	return h
}

func (h *HttpClient) SetDownloadProgress(progress DownloadProgress) *HttpClient {
	if progress != nil {
		h.downloadProgress = progress
	}
	return h
}

func (h *HttpClient) SetDownloadFinish(finish DownloadFinish) *HttpClient {
	if finish != nil {
		h.downloadFinish = finish
	}
	return h
}

func (h *HttpClient) methodToStr(method HttpMethod) string {
	switch method {
	case HTTP_GET:
		return "GET"
	case HTTP_POST:
		return "POST"
	case HTTP_PUT:
		return "PUT"
	default:
		return ""
	}
}

func (h *HttpClient) bodyReader() io.Reader {
	if h.form != nil || len(h.form) > 0 {
		values := url.Values{}
		for k, v := range h.form {
			values.Set(k, v)
		}
		h.body = values.Encode()
	}
	return strings.NewReader(h.body)
}

func (h *HttpClient) sendRequest(body io.Reader) (*http.Response, error) {
	if h.err != nil {
		return nil, h.err
	}
	if h.url == "" {
		return nil, exception.NewExpectError("request url is empty")
	}
	method := h.methodToStr(h.method)
	if method == "" {
		return nil, exception.NewExpectError("request method is invalid")
	}

	req, err := http.NewRequest(method, h.url, body)
	if err != nil {
		return nil, err
	}
	if h.header != nil {
		for k, v := range h.header {
			req.Header.Add(k, v)
		}
	}

	client := http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		},
		Timeout: time.Second * 60,
	}
	rsp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	if err = h.callStatusCodeHandle(rsp.StatusCode); err != nil {
		return nil, err
	}
	return rsp, nil
}

func (h *HttpClient) createResponse(rsp *http.Response) (*HttpResponse, error) {
	response, err := h.createResponseUnreadBody(rsp)
	if err != nil {
		return nil, err
	}

	defer util.CloseQuietly(rsp.Body)
	rspBody, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return nil, err
	}
	response.Body = rspBody
	return response, nil
}

func (h *HttpClient) createResponseUnreadBody(rsp *http.Response) (*HttpResponse, error) {
	response := NewHttpResponse()
	response.StatusCode = rsp.StatusCode

	if rsp.Header != nil {
		for k, v := range rsp.Header {
			response.Header[k] = v
		}
	}
	return response, nil
}

func (h *HttpClient) findValueInHeader(header map[string][]string, key string) []string {
	if header == nil || key == "" {
		return nil
	}
	key2 := strings.Replace(key, "-", "", -1)
	for k, v := range header {
		if strings.EqualFold(k, key) || strings.EqualFold(k, key2) {
			return v
		}
	}
	return nil
}

func (h *HttpClient) callStatusCodeHandle(statusCode int) error {
	if h.statusCodeHandle == nil {
		return nil
	}
	return h.statusCodeHandle(statusCode)
}

func (h *HttpClient) Send() (*HttpResponse, error) {
	rsp, err := h.sendRequest(h.bodyReader())
	if err != nil {
		return nil, err
	}
	response, err := h.createResponse(rsp)
	if err != nil {
		return nil, err
	}
	return response, nil
}

func (h *HttpClient) Upload(filePath string) (*HttpResponse, error) {
	if !util.IsExists(filePath) {
		return nil, exception.NewExpectError("upload file not found")
	}
	fileInfo, err := os.Stat(filePath)
	if err != nil {
		return nil, err
	}

	buf := new(bytes.Buffer)
	bodyWriter := multipart.NewWriter(buf)
	defer util.CloseQuietly(bodyWriter)

	if h.form != nil || len(h.form) > 0 {
		for k, v := range h.form {
			if err = bodyWriter.WriteField(k, v); err != nil {
				return nil, err
			}
		}
	}

	part, err := bodyWriter.CreateFormFile("file", fileInfo.Name())
	if err != nil {
		return nil, err
	}

	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer util.CloseQuietly(file)

	if _, err = io.Copy(part, file); err != nil {
		return nil, err
	}

	h.AddHeader("Content-Type", bodyWriter.FormDataContentType())

	bodyWriter.Close()

	rsp, err := h.sendRequest(buf)
	if err != nil {
		return nil, err
	}

	response, err := h.createResponse(rsp)
	if err != nil {
		return nil, err
	}
	return response, nil
}

func (h *HttpClient) Download(dirPath string, defaultFilename string) (response *HttpResponse, err error) {
	response = nil
	err = nil

	rsp, err := h.sendRequest(h.bodyReader())
	if err != nil {
		return
	}
	response, err = h.createResponseUnreadBody(rsp)
	if err != nil {
		return
	}

	// Content-Disposition
	var contentDisposition string
	contentDispositions := h.findValueInHeader(response.Header, "Content-Disposition")
	if contentDispositions != nil && len(contentDispositions) > 0 {
		contentDisposition = contentDispositions[0]
	}
	filename := h.generateFilename(contentDisposition, defaultFilename)

	// Content-Length
	var contentLength int64
	contentLengths := h.findValueInHeader(response.Header, "Content-Length")
	if contentLengths != nil && len(contentLengths) > 0 {
		if contentLength, err = strconv.ParseInt(contentLengths[0], 10, 64); err != nil {
			return
		}
	}

	if !util.IsExists(dirPath) {
		if err = os.MkdirAll(dirPath, 0666); err != nil {
			return
		}
	}
	filePath := util.PathJoin(dirPath, filename)
	file, err := os.Create(filePath)
	if err != nil {
		return
	}

	defer func() {
		rsp.Body.Close()
		file.Close()
	}()

	if h.downloadStart != nil {
		h.downloadStart(dirPath, filename, contentLength)
	}

	w, err := util.Copy(file, rsp.Body, func(num, written int64) {
		if h.downloadProgress != nil {
			h.downloadProgress(num, written, contentLength)
		}
	})
	if h.downloadFinish != nil {
		h.downloadFinish(w, contentLength, err)
	}
	if err != nil {
		return
	}
	return
}

func (h *HttpClient) generateFilename(contentDisposition, defaultFilename string) string {
	filename := FetchFilenameInContentDisposition(contentDisposition)
	if filename != "" {
		return filename
	}
	if defaultFilename != "" {
		return defaultFilename
	}
	return util.NowFormat(util.DATETIME_FMT)
}

// Content-Disposition: attachment; filename="DOCX%20%e6%96%87%e6%a1%a3.docx"; filename*=utf-8''DOCX%20%e6%96%87%e6%a1%a3.docx
func FetchFilenameInContentDisposition(contentDisposition string) string {
	if contentDisposition == "" {
		return ""
	}
	array := strings.Split(contentDisposition, ";")
	if array == nil || len(array) == 0 {
		return ""
	}
	for _, value := range array {
		value = strings.TrimSpace(value)
		index := strings.Index(value, "filename=")
		if index >= 0 {
			filename := strings.TrimSpace(value[index+9:])
			if strings.HasPrefix(filename, "\"") {
				filename = filename[1:]
			}
			if strings.HasSuffix(filename, "\"") {
				filename = filename[0 : len(filename)-1]
			}
			return filename
		}
	}
	return ""
}

func Get(url string, outData interface{}) error {
	client := NewHttpClient().SetUrl(url).SetMethod(HTTP_GET)
	rsp, err := client.Send()
	if err != nil {
		return err
	}
	if err = rsp.GetBodyJSON(outData); err != nil {
		return err
	}
	return nil
}

func GetWithToken(url, token string, outData interface{}) error {
	if token == "" {
		return exception.NewExpectError("request get failed: token is empty")
	}
	client := NewHttpClient().SetUrl(url).SetMethod(HTTP_GET).AddHeader("Authorization", "Bearer "+token)
	rsp, err := client.Send()
	if err != nil {
		return err
	}
	if err = rsp.GetBodyJSON(outData); err != nil {
		return err
	}
	return nil
}

func Post(url string, body interface{}, outData interface{}) error {

	client := NewHttpClient().SetUrl(url).SetMethod(HTTP_POST).AddHeader("Content-Type", "application/json").SetBodyJSON(body)
	rsp, err := client.Send()
	if err != nil {
		return err
	}
	if err = rsp.GetBodyJSON(outData); err != nil {
		return err
	}
	return nil
}

func PostWithToken(url, token string, body interface{}, outData interface{}) error {
	if token == "" {
		return exception.NewExpectError("request post failed: token is empty")
	}

	client := NewHttpClient().SetUrl(url).SetMethod(HTTP_POST).AddHeader("Content-Type", "application/json").AddHeader("Authorization", "Bearer "+token).SetBodyJSON(body)
	rsp, err := client.Send()
	if err != nil {
		return err
	}
	if err = rsp.GetBodyJSON(outData); err != nil {
		return err
	}
	return nil
}
